package model;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Classe representant le serveur d'ecoute TCP
 *
 * @author Lefevre, Thoue
 */
public class Serveur extends Thread {

    private int port;
    private int nbClient;
    private Manager manager;
    private Vector listeClients = new Vector(); // contiendra tous les flux de sortie vers les clients
    private Vector<Conversation> conversations;

    /**
     * Constructeur utiliser pour ecouter sur le port par defaut (2012)
     *
     * @param manager lien vers le manager des threads
     */
    public Serveur(Manager manager) {
        this.port = 2012;
        this.manager = manager;
        conversations = new Vector<Conversation>();
    }

    /**
     * Constructeur secondaire
     *
     * @param manager lien vers le manager des threads
     * @param port port ecouter par le serveur
     */
    public Serveur(Manager manager, int port) {
        this.port = port;
        this.manager = manager;
        conversations = new Vector<Conversation>();
    }

    /**
     * Methode principal du thread - Elle instancie une conversation a chaque
     * connection d'un client.
     */
    @Override
    public void run() {
        ServerSocket ss = null;
        try {
            ss = new ServerSocket(this.port); // ouverture d'un socket serveur sur port
        } catch (IOException ex) {
            Logger.getLogger(Serveur.class.getName()).log(Level.SEVERE, "Ouverture du port serveur incorrect.", ex);
        }
        while (true) // attente en boucle de connexion (bloquant sur ss.accept)
        {
            try {
                Logger.getLogger(Serveur.class.getName()).log(Level.INFO, "Demarrage du serveur - Attente de demande.");
                Socket socket = ss.accept();
                if (!conversationAlreadyExist(socket.getInetAddress().getHostAddress())) {
                    Conversation conversation = new Conversation(socket, this); // un client se connecte, un nouveau thread client est lance
                    conversations.add(conversation);
                    conversation.start();
                }
            } catch (IOException ex) {
                Logger.getLogger(Serveur.class.getName()).log(Level.SEVERE, "Initialisation d'une conversation incorrect.", ex);
            }
        }
    }

    /**
     * Instancie une nouvelle conversation
     *
     * @param ip adresse ip du client
     * @param port port du client
     */
    public void nouvelleConversation(String ip, int port) {
        Socket socket = null;
        if (!conversationAlreadyExist(ip)) {
            try {
            socket = new Socket(ip, port);
            Conversation conversation = new Conversation(socket, this); // un client se connecte, un nouveau thread client est lance
            conversations.add(conversation);
            conversation.start();
            sendTo(new Signalisation(manager.getModel().getCurrentUser()), conversation.getIdClient());
            }  catch (ConnectException ex) {
                Logger.getLogger(Serveur.class.getName()).log(Level.INFO,"L'hote distant n'est pas disponible");
            }  catch (UnknownHostException ex) {
                Logger.getLogger(Serveur.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(Serveur.class.getName()).log(Level.SEVERE, null, ex);
            }            
        }

    }

    /**
     * Getter nbClient
     *
     * @return le nombre actuel de client
     */
    public int getNbClient() {
        return nbClient;
    }

    /**
     * Getter port
     *
     * @return le port d'ecoute du serveur
     */
    public int getPort() {
        return port;
    }

    /**
     * Getter manager
     *
     * @return le manager des threads
     */
    public Manager getManager() {
        return manager;
    }

    /**
     * Getters conversations
     *
     * @return la liste des conversations
     */
    public Vector<Conversation> getConversations() {
        return conversations;
    }

    /**
     * Ajoute un nouveau client
     *
     * @param out flux de sortie du client
     * @return l'identifiant du nouveau client
     */
    public int addClient(OutputStream out) {

        listeClients.addElement(out);
        Logger.getLogger(Serveur.class.getName()).log(Level.INFO, "Ajout du client n\u00b0{0} au serveur.", nbClient);
        nbClient++;
        return nbClient - 1;
    }

    /**
     * supprimme un client
     *
     * @param int identifiant du client
     */
    public void deleteClient(int id) {
        manager.avertirFinAppel(conversations.get(id).getUser().getIp(),conversations.get(id).getUser().getPort());
        listeClients.remove(id);
        conversations.remove(id);
        if (manager.getConversations().size() > id) {
            manager.getConversations().remove(id);
        }
        if (nbClient > 0) {
            nbClient--;
        }        
    }

    /**
     * Envoie un message de signalisation a tout les clients
     *
     * @param message message a envoyer
     */
    public void sendAll(Signalisation message) {
        // declaration d'une variable permettant l'envoi de texte vers le client
        for (int i = 0; i < listeClients.size(); i++) // parcours de la table des conversations
        {
            sendTo(message, i);
        }
    }

    /**
     * Envoie un message de signalisation a un client
     *
     * @param message message a envoyer
     * @param idClient identifiant du client destinataire
     */
    public void sendTo(Signalisation message, int idClient) {
        if (conversations.get(idClient).getStatus() != EStatusConversation.FIN) {

            ObjectOutputStream out;
            out = (ObjectOutputStream) listeClients.elementAt(idClient); // extraction de l'element courant (type PrintWriter)
            if (out != null) {
                Logger.getLogger(Serveur.class.getName()).log(Level.INFO, "message:{0}", message.getTagMessage().toString());
                try {
                    out.writeObject(message);
                    out.flush(); // envoi dans le flux de sortie
                } catch (IOException ex) {
                    Logger.getLogger(Serveur.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    /**
     * Test si la conversation existe deja dans le serveur
     *
     * @param ip adresse a teste
     * @return vrai si l'ip existe
     */
    public boolean conversationAlreadyExist(String ip) {
        boolean result = false;
        for (Conversation c : conversations) {
            if (ip.equals(c.getSocketClient().getInetAddress().getHostAddress())) {
                result = true;
            }
        }       
        return result;
    }
}
